﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using SlimDX;
using SlimDX.D3DCompiler;
using SlimDX.Direct3D10;
using Buffer = SlimDX.Direct3D10.Buffer;
using System;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Classe helper pour l'interaction avec le <see cref="SlimDX.Direct3D10.Device"/> DirectX10
    /// </summary>
    public static class DeviceHelper
    {
        /// <summary>
        /// Crée un <see cref="SlimDX.Direct3D10.Buffer"/> à partir d'une liste de donnnées
        /// </summary>
        /// <typeparam name="TData">Type des données à intégrer dans le buffer</typeparam>
        /// <param name="dataList">Liste des données à intégrer dans le buffer</param>
        /// <param name="bufferUsage">Type d'utilisation du buffer</param>
        /// <returns>Renvoie une <see cref="SlimDX.Direct3D10.Buffer"/> utilisable dans le pipeline DirectX10</returns>
        public static SlimDX.Direct3D10.Buffer CreateBuffer<TData>(this IEnumerable<TData> dataList, BindFlags bufferUsage)
        {
            SlimDX.Direct3D10.Buffer returnValue;

            BufferDescription bufferDescription;
            bufferDescription = new BufferDescription();
            bufferDescription.BindFlags = bufferUsage;
            bufferDescription.CpuAccessFlags = CpuAccessFlags.None;
            bufferDescription.OptionFlags = ResourceOptionFlags.None;
            bufferDescription.SizeInBytes = dataList.Count() * Marshal.SizeOf(typeof(TData));
            bufferDescription.Usage = ResourceUsage.Default;

            using (var stream = new DataStream(dataList.ToArray(), true, true))
            {
                returnValue = new SlimDX.Direct3D10.Buffer(Engine.Device10, stream, bufferDescription);
            }

            return returnValue;
        }

        /// <summary>
        /// Crée un layout d'entrée à partir d'un effet
        /// </summary>
        /// <param name="effect">Effet à utiliser pour créer le layout</param>
        /// <param name="techniqueName">Technique à utiliser pour créer le layout</param>
        /// <param name="inputElements">Liste des éléments du layout</param>
        /// <returns>Retourne le layout d'entrée</returns>
        public static InputLayout CreateInputLayout(this SlimDX.Direct3D10.Effect effect, string techniqueName, InputElement[] inputElements)
        {
            if (object.ReferenceEquals(effect, null))
                throw new ArgumentNullException("effect");
            if (string.IsNullOrEmpty(techniqueName))
                throw new ArgumentNullException("techniqueName");
            if (object.ReferenceEquals(inputElements, null))
                throw new ArgumentNullException("inputElements");

            EffectTechnique technique = effect.GetTechniqueByName(techniqueName);
            return technique.CreateInputLayout(inputElements);
        }

        /// <summary>
        /// Crée un layout d'entrée à partir d'une technique
        /// </summary>
        /// <param name="technique">Technique à utiliser pour créer le layout</param>
        /// <param name="inputElements">Liste des éléments du layout</param>
        /// <returns>Retourne le layout d'entrée</returns>
        public static InputLayout CreateInputLayout(this EffectTechnique technique, InputElement[] inputElements)
        {
            if (object.ReferenceEquals(technique, null))
                throw new ArgumentNullException("technique");
            if (object.ReferenceEquals(inputElements, null))
                throw new ArgumentNullException("inputElements");

            EffectPass firstPass = technique.GetPassByIndex(0);
            return firstPass.CreateInputLayout(inputElements);
        }

        /// <summary>
        /// Crée un layout d'entrée à partir d'une passe
        /// </summary>
        /// <param name="pass">Passe à utiliser pour créer le layout</param>
        /// <param name="inputElements">Liste des éléments du layout</param>
        /// <returns>Retourne le layout d'entrée</returns>
        public static InputLayout CreateInputLayout(this EffectPass pass, InputElement[] inputElements)
        {
            if (object.ReferenceEquals(pass, null))
                throw new ArgumentNullException("pass");
            if (object.ReferenceEquals(inputElements, null))
                throw new ArgumentNullException("inputElements");

            return pass.Description.CreateInputLayout(inputElements);
        }

        /// <summary>
        /// Crée un layout d'entrée à partir d'une description de passe
        /// </summary>
        /// <param name="description">Description de passe à utiliser pour créer le layout</param>
        /// <param name="inputElements">Liste des éléments du layout</param>
        /// <returns>Retourne le layout d'entrée</returns>
        public static InputLayout CreateInputLayout(this EffectPassDescription description, InputElement[] inputElements)
        {
            if (object.ReferenceEquals(description, null))
                throw new ArgumentNullException("description");
            if (object.ReferenceEquals(inputElements, null))
                throw new ArgumentNullException("inputElements");

            return description.Signature.CreateInputLayout(inputElements);
        }

        /// <summary>
        /// Crée un layout d'entrée à partir d'une signature de shader
        /// </summary>
        /// <param name="signature">Signature de shader à utiliser pour créer le layout</param>
        /// <param name="inputElements">Liste des éléments du layout</param>
        /// <returns>Retourne le layout d'entrée</returns>
        public static InputLayout CreateInputLayout(this ShaderSignature signature, InputElement[] inputElements)
        {
            if (object.ReferenceEquals(signature, null))
                throw new ArgumentNullException("signature");
            if (object.ReferenceEquals(inputElements, null))
                throw new ArgumentNullException("inputElements");

            InputLayout returnValue = null;
            InputLayout temp = null;

            try
            {
                temp = new InputLayout(Engine.Device10, signature, inputElements);
                returnValue = temp;
                temp = null;
            }
            finally
            {
                if (temp != null)
                {
                    temp.Dispose();
                    temp = null;
                }
            }

            return returnValue;
        }
    }
}
